import xml.etree.ElementTree as ET
from typing import Type,Union,Optional,TypeVar,Generic
from feasytools import RangeList
from .params import *
from .evdict import EVDict
from .cs import CS,SCS,FCS

T_CS = TypeVar("T_CS",FCS,SCS)
CS_Type = Type[T_CS]

class CSList(Generic[T_CS]):
    '''
    充电站列表。编号(下标)从0开始。
    '''
    _cs:list[T_CS]
    def __iter__(self): return self._cs.__iter__()
    def __init__(self,ev_dict_to_bind:EVDict,*,csList:Optional[list[T_CS]]=None,filePath:Optional[str]=None,csType:CS_Type=FCS):
        '''
        初始化
            ev_dict_to_bind: 绑定的EVDict
            csList: 充电站列表
            filePath: 充电站文件
            csType: 充电站类型, 只能是FCS或SCS
            提供csList, 或者同时提供filePath和csType
        '''
        self._evdict:EVDict = ev_dict_to_bind
        if csList is not None:
            flagF = False; flagS = False
            for cs in csList:
                if isinstance(cs,FCS): flagF = True
                elif isinstance(cs,SCS): flagS = True
                else: raise TypeError("csList中的元素必须是FCS或SCS")
            if flagF and flagS and not ALLOW_MIXED_CSTYPE_IN_CSLIST:
                raise TypeError("CSList禁止同时存在FCS和SCS元素, 将simparams.ALLOW_MIXED_CSTYPE_IN_CSLIST设为True以移除此限制")
            self._cs = csList
        elif filePath is not None and csType is not None:
            assert csType == FCS or csType == SCS
            self._cs=[]
            for cs_node in ET.ElementTree(file = filePath).getroot():
                par_pbuy=None;par_psell=None;par_off=[]
                for cfg in cs_node:
                    if cfg.tag=="pbuy" or cfg.tag=="psell":
                        tl=[int(itm.attrib['btime']) for itm in cfg]
                        pl=[float(itm.attrib["price"]) for itm in cfg]
                        if cfg.tag=="pbuy": par_pbuy=(tl,pl)
                        else: par_psell=(tl,pl)
                    elif cfg.tag=="offline":
                        par_off=RangeList(cfg)
                    else:
                        raise ValueError(f"用xml文件初始化CSList时, 遇到无效的tag{cfg.tag}")
                if par_pbuy is None:
                    raise ValueError(f"用xml文件初始化CSList时, 未指定购电价格")
                self._cs.append(csType(cs_node.attrib["name"],int(cs_node.attrib["slots"]),cs_node.attrib["bus"],par_pbuy,par_psell,par_off))
        else:
            raise ValueError("无效的CSList初始化参数类型")
        self._n=len(self._cs)
        self._remap:dict[str,int]={} # 某名称CS的编号
        self._veh:dict[str,int]={} # 某辆车所属的充电站编号
        self._cs_names=[cs.name for cs in self._cs]
        self._cs_slots=[cs.slots for cs in self._cs]
        self.__v2g_cap_res:list[float] = [0.] * self._n
        self.__v2g_cap_res_time = -1
        self.__v2g_demand:list[float] = []
        for i,cs in enumerate(self._cs):
            self._remap[cs.name]=i
    
    def index(self,name:str)->int:
        '''根据充电站名称获得其编号。若指定名称不存在, 产生KeyError。'''
        return self._remap[name]
    
    def get_CS_names(self)->list[str]:
        '''所有充电站的名称列表'''
        return self._cs_names
    
    def get_online_CS_names(self, t:int)->list[str]:
        '''t秒时所有可用的充电站的名称列表'''
        return [cs.name for cs in self._cs if cs.is_online(t)]
    
    def get_prices_at(self,t:int)->list[float]:
        '''t秒时所有充电站的价格列表'''
        return [cs.pbuy(t) for cs in self._cs]
    
    def get_online_prices_at(self,t:int)->list[float]:
        '''t秒时所有可用的充电站的价格列表'''
        return [cs.pbuy(t) for cs in self._cs if cs.is_online(t)]

    def get_veh_count(self, only_charging=False)->list[int]:
        '''所有充电站的车辆数列表. 当only_charging为True时, 只返回正在充电的车辆数'''
        return [cs.veh_count(only_charging) for cs in self._cs]
    
    def get_online_veh_count(self,t:int, only_charging=False)->list[int]:
        '''t秒时所有可用的充电站的车辆数列表. 当only_charging为True时, 只返回正在充电的车辆数'''
        return [cs.veh_count(only_charging) for cs in self._cs if cs.is_online(t)]
    
    def get_slots_of(self)->list[int]:
        '''返回所有充电站的充电桩数量列表'''
        return self._cs_slots
    
    def get_online_slots_of(self,t:int)->list[int]:
        '''t秒时所有可用的充电站的充电桩数量列表'''
        return [cs.slots for cs in self._cs if cs.is_online(t)]
    
    def get_Pd(self, k:float = 1)->list[float]:
        '''
        所有充电站的充电功率列表, 默认单位kWh/s
            k: 单位转换系数, 默认为1, 表示不转换; 3600表示转换为kW; 3600/Sb_kVA表示转换为标幺值
        '''
        return [cs.Pd*k for cs in self._cs]
    
    def get_online_Pd(self, t:int, k:float = 1)->list[float]:
        '''
        所有可用的充电站的充电功率列表
            t: 判定时间
            sb: 单位转换系数, 默认为1, 表示不转换; 3600表示转换为kW; 3600/Sb_kVA表示转换为标幺值
        '''
        return [cs.Pd*k for cs in self._cs if cs.is_online(t)]
    
    def get_Pc(self, k:float = 1)->list[float]:
        '''
        所有充电站的放电功率列表
            k: 单位转换系数, 默认为1, 表示不转换; 3600表示转换为kW; 3600/Sb_kVA表示转换为标幺值
        '''
        return [cs.Pc*k for cs in self._cs]
    
    def get_online_Pc(self,t:int, k:float = 1)->list[float]:
        '''
        所有可用的充电站的放电功率列表
            t: 判定时间
            sb: 单位转换系数, 默认为1, 表示不转换; 3600表示转换为kW; 3600/Sb_kVA表示转换为标幺值
        '''
        return [cs.Pc*k for cs in self._cs if cs.is_online(t)]
    
    def add_veh(self,veh_id:str,cs:Union[int,str])->bool:
        '''
        添加车辆至指定充电站
            veh_id: 车辆ID
            cs: 充电站名称或字符串
        返回:
            True如果添加成功,
            False如果该车已在充电,
            产生KeyError如果充电站不存在.
        '''
        if not isinstance(cs,int): cs=self.index(cs)
        ret=self._cs[cs].add_veh(veh_id)
        if ret: self._veh[veh_id]=cs
        return ret
    
    def pop_veh(self,veh_id:str)->bool:
        '''
        从充电站中移除车辆
            veh_id: 车辆ID
        返回:
            True如果移除成功，
            False如果该车不存在。
        '''
        try:
            cs=self._veh[veh_id]
        except KeyError:
            return False
        del self._veh[veh_id]
        self._cs[cs].pop_veh(veh_id)
        return True

    def has_veh(self,veh_id:str)->bool:
        '''
        检查是否有指定ID的车辆
            veh_id: 车辆ID
        返回:
            True如果存在，
            False如果不存在。
        '''
        return veh_id in self._veh

    def CS_index(self,cs_name:str)->int:
        '''
        检查指定名称的充电站的下标（从0开始）
            cs_name: 充电站名称
        返回:
            下标。如果不存在，返回-1。
        '''
        try:
            return self._cs_names.index(cs_name)
        except ValueError:
            return -1

    def is_charging(self,veh_id:str)->bool:
        '''
        获取车辆充电状态。如果车辆不存在则报错ValueError。
            veh_id: 车辆ID
        返回:
            True如果正在充电，
            False如果正在等待。
        '''
        cs=self._veh[veh_id]
        return self._cs[cs].is_charging(veh_id)
    
    def __getitem__(self,indices:Union[str,int])->CS:
        '''
        获取指定下标的充电站。可以是不同下标，也可以是充电站名称。
        '''
        if isinstance(indices,str): indices=self._remap[indices]
        return self._cs[indices]
    
    def get_V2G_cap(self,t:int)->list[float]:
        '''
        获取当前时刻各充电站最大V2G返送功率(已经考虑损耗), 单位为kWh/s
            t: 指示当前时刻
        '''
        if t == self.__v2g_cap_res_time: return self.__v2g_cap_res
        self.__v2g_cap_res = [cs.get_V2G_cap(self._evdict) for cs in self._cs]
        self.__v2g_cap_res_time = t
        return self.__v2g_cap_res
    
    def set_V2G_demand(self,v2g_demand:list[float]):
        '''设置V2G需求'''
        assert len(v2g_demand) == len(self._cs) or len(v2g_demand) == 0
        self.__v2g_demand = v2g_demand
        
    def update(self,sec:int,cur_time:int) -> list[str]:
        '''
        以当前参数, 对EV进行充电和V2G放电。
            sec: 充电时长为sec秒
            cur_time: 当前时间
        返回：
            完成充电的车辆列表
            (功率均以kWh/s计算)
        '''
        if len(self.__v2g_demand)>0: # 有V2G需求
            v2g_cap = self.get_V2G_cap(sec)
            # 各CS的实际V2G返送功率与其最大V2G返送功率之比
            v2g_k = [min(1,d/c) if c>0. else 0. for d,c in zip(self.__v2g_demand,v2g_cap)]
        else: 
            v2g_k = [0] * len(self._cs)
        ret:list[str]=[]
        for cs, k in zip(self._cs,v2g_k):
            lst = cs.update(self._evdict,sec,cur_time,k)
            for veh_id in lst:
                del self._veh[veh_id]
            ret.extend(lst)
        return ret
    
    def __len__(self):
        return len(self._cs)
    
    def __str__(self):
        return f"CSList[{','.join(map(str,self._cs))}]"